<?php

namespace common\rest;

use Yii;
use yii\data\DataProviderInterface;
use yii\data\Pagination;
use yii\data\Sort;
use yii\web\Link;

/**
 * Serializer converts resource objects and collections into array representation.
 *
 * Serializer is mainly used by REST controllers to convert different objects into array representation
 * so that they can be further turned into different formats, such as JSON, XML, by response formatters.
 *
 * The default implementation handles resources as [[Model]] objects and collections as objects
 * implementing [[DataProviderInterface]]. You may override [[serialize()]] to handle more types.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Serializer extends \yii\rest\Serializer {

    /**
     * @var string the name of the envelope (e.g. `_links`) for returning the links objects.
     * It takes effect only, if `collectionEnvelope` is set.
     * @since 2.0.4
     */
    public $sortsEnvelope = null;

    /**
     * @var string the name of the envelope (e.g. `_links`) for returning the links objects.
     * It takes effect only, if `collectionEnvelope` is set.
     * @since 2.0.4
     */
    public $labelsEnvelope = null;

    /**
     * @var string the name of the envelope (e.g. `_links`) for returning the links objects.
     * It takes effect only, if `collectionEnvelope` is set.
     * @since 2.0.4
     */
    public $randEnvelope = false;

    /**
     * Serializes a pagination into an array.
     * @param \yii\db\ActiveQueryInterface $query
     * @return array the array representation of the pagination
     * @see addPaginationHeaders()
     */
    protected function serializeLabels($query) {
        $labels = [];
        /* @var $model \yii\db\ActiveRecordInterface */
        $modelClass = $query->modelClass;
        $model = $modelClass::instance();
        $fields = $model->fields();
        if (empty($fields)) {
            $fields = array_keys($model->attributes);
        }
        foreach ($fields as $attribute) {
            $labels[$attribute] = $model->getAttributeLabel($attribute);
        }
        return $labels;
    }

    /**
     * Serializes a pagination into an array.
     * @param Sort $sort
     * @return array the array representation of the pagination
     * @see addPaginationHeaders()
     */
    protected function serializeSort($sort) {
        $attributes = $sort->attributes;
        return [
            $this->sortsEnvelope => $attributes
        ];
    }

    /**
     * Serializes a data provider.
     * @param DataProviderInterface $dataProvider
     * @return array the array representation of the data provider.
     */
    protected function serializeDataProvider($dataProvider) {
        if ($this->preserveKeys) {
            $models = $dataProvider->getModels();
        } else {
            $models = array_values($dataProvider->getModels());
        }
        $models = $this->serializeModels($models);
        $pagination = $dataProvider->getPagination();
        if ($pagination !== false) {
            $this->addPaginationHeaders($pagination);
        }
        if ($this->request->getIsHead()) {
            return null;
        } elseif ($this->collectionEnvelope === null) {
            return $models;
        }

        $result = [
            $this->collectionEnvelope => $models,
        ];

        if ($pagination !== false) {
            $result = array_merge($result, $this->serializePagination($pagination));
        }

        if ($dataProvider instanceof \yii\data\ActiveDataProvider && $dataProvider->query instanceof \yii\db\ActiveQueryInterface && $this->labelsEnvelope) {
            $result[$this->labelsEnvelope] = $this->serializeLabels($dataProvider->query);
        }

        $sort = $dataProvider->getSort();
        if ($sort !== false && $this->sortsEnvelope) {
            $result = array_merge($result, $this->serializeSort($sort));
        }

        return $result;
    }

    /**
     * Serializes a pagination into an array.
     * @param Pagination $pagination
     * @return array the array representation of the pagination
     * @see addPaginationHeaders()
     */
    protected function serializePagination($pagination) {
        $result = [];
        if ($this->linksEnvelope) {
            $result[$this->linksEnvelope] = Link::serialize($pagination->getLinks(true));
        }
        if ($this->metaEnvelope) {
            $result[$this->metaEnvelope] = [
                'totalCount' => $pagination->totalCount,
                'pageCount' => $pagination->getPageCount(),
                'currentPage' => $pagination->getPage() + 1,
                'perPage' => $pagination->getPageSize(),
            ];
        }
        return $result;
    }

    /**
     * Serializes a set of models.
     * @param array $models
     * @return array the array representation of the models
     */
    protected function serializeModels(array $models) {
        if ($this->randEnvelope) {
            shuffle($models);
        }
        return parent::serialize($models);
    }

}
